﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows.Input;

namespace VisualTreeInspector
{
    /// <summary>
    /// Represents a class used to inspect the visual tree of
    /// a Silverlight application.
    /// </summary>
    public partial class Inspector : UserControl
    {
        DispatcherTimer timer;
        bool hasCompleted;

        /// <summary>
        /// Sets the ItemsSource of the root TreeView.
        /// </summary>
        /// <param name="obj">The IEnumerable that is to be 
        /// the TreeView ItemsSource.</param>
        private void SetItemsSource(IEnumerable obj)
        {
            TreeV.ItemsSource = obj;
        }

        public static readonly DependencyProperty VisualRootProperty =
            DependencyProperty.Register("VisualRoot", typeof(DependencyObject), typeof(Inspector), new PropertyMetadata(null, OnVisualRootChanged));

        private static void OnVisualRootChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            (sender as Inspector).OnVisualRootChanged(e);
        }

        private void OnVisualRootChanged(DependencyPropertyChangedEventArgs e)
        {
            RebuildVisualTreeMap(true);
        }

        public DependencyObject VisualRoot
        {
            get
            {
                return (DependencyObject)GetValue(VisualRootProperty);
            }
            set
            {
            	SetValue(VisualRootProperty, value);
            }
        }

        /// <summary>
        /// Rebuilds the visual tree map. Returns if a previous pass,
        /// as indicated by hasCompleted, has not finished.
        /// </summary>
        /// <param name="forceReloadOfTree">Rebuild the tree even if
        /// nothing has changed.</param>
        private void RebuildVisualTreeMap(bool forceReloadOfTree)
        {
            if (!hasCompleted)
                return;

            try
            {
                hasCompleted = false;
                List<VisualStructure> Structure = new List<VisualStructure>();
                VisualStructure structureCurrent = null;
                if (VisualRoot != null)
                {
                    structureCurrent = new VisualStructure(VisualRoot);


                }
                else if (this.Parent != null)
                {

                    structureCurrent = new VisualStructure(this.Parent);

                }
                Structure.Add(structureCurrent);

                List<VisualStructure> oldStructure = TreeV.ItemsSource as List<VisualStructure>;

                if (oldStructure != null && !forceReloadOfTree)
                    if (oldStructure.Count > 0)
                        if (oldStructure[0].TotalChildren == structureCurrent.TotalChildren)
                        {
                            timer.Interval = TimeSpan.FromSeconds(4);
                            return;
                        }

                timer.Interval = TimeSpan.FromSeconds(1);

                Dispatcher.BeginInvoke(new Action<IEnumerable>(SetItemsSource), Structure);
            }
            catch (Exception ex)
            {
            }
            finally
            {
                hasCompleted = true;
            }
        }

        /// <summary>
        /// Creates a new instance of the Inspector control.
        /// </summary>
        public Inspector()
        {
            InitializeComponent();
            this.Loaded += OnLoaded;
        }

        /// <summary>
        /// Wires up the various timers and events to handle.
        /// </summary>
        /// <param name="sender">The originating object sending the event.</param>
        /// <param name="e">EventArgs associated with this event.</param>
        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            TreeV.SelectedItemChanged += OnSelectedItemChanged;
            hasCompleted = true;
            timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromSeconds(1);
            timer.Tick += new EventHandler(timer_Tick);
            timer.Start();
            this.Loaded -= OnLoaded;
        }

        /// <summary>
        /// Represents a popup overlay that paints a faint border
        /// on a specific control.
        /// </summary>
        Popup fakeAdorner = null;

        /// <summary>
        /// Creates the fake adorner to overlay on other controls.
        /// </summary>
        private void CreateFakeAdorner()
        {
            if (fakeAdorner == null)
            {
                fakeAdorner = new Popup();
                fakeAdorner.IsHitTestVisible = false;
                fakeAdorner.Child = new Border()
                {
                    HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
                    VerticalAlignment = System.Windows.VerticalAlignment.Stretch,
                    BorderBrush = new SolidColorBrush(Color.FromArgb(64, 255, 0, 0)),
                    BorderThickness = new Thickness(5.0)
                };
            }
        }

        public static readonly DependencyProperty SelectedItemProperty =
            DependencyProperty.Register("SelectedItem", typeof(Object), typeof(Inspector), new PropertyMetadata(null));

        public object SelectedItem
        {
            get
            {
                return GetValue(SelectedItemProperty);
            }
            set
            {
                SetValue(SelectedItemProperty, value);
            }
        }
        

        /// <summary>
        /// Updates the adorner position.
        /// </summary>
        /// <param name="sender">The originating object.</param>
        /// <param name="e">The arguments associated with this event.</param>
        private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {

            VisualStructure structure = e.NewValue as VisualStructure;
            this.SelectedItem = e.NewValue;
            
            if (structure == null)
                return;
            
            int hash = structure.HashCode;

            DependencyObject visualItemSelected = InspectorHelpers.FindElementByHashCode(Application.Current.RootVisual, hash);

            if (visualItemSelected == null)
                return;

            CreateFakeAdorner();

            FrameworkElement visual = visualItemSelected as FrameworkElement;
            if (visual == null)
            {
                if (fakeAdorner.IsOpen)
                    fakeAdorner.IsOpen = false;

                return;
            }

            Point screenPosition = InspectorHelpers.GetPositionAbsolute(visual);
                        
            double newHeight = visual.ActualHeight;
            double newWidth = visual.ActualWidth;

            Thickness newMargin = new Thickness(screenPosition.X,
                                      screenPosition.Y,
                                      0,
                                      0);

            Border adornerBorder = fakeAdorner.Child as Border;
            adornerBorder.Width = newWidth;
            adornerBorder.Height = newHeight;
            fakeAdorner.Margin = newMargin;

            if (!fakeAdorner.IsOpen)
                fakeAdorner.IsOpen = true;

        }

        /// <summary>
        /// Rebuilds the visual tree.
        /// </summary>
        /// <param name="sender">The originating object.</param>
        /// <param name="e">The arguments associated with this event.</param>
        void timer_Tick(object sender, EventArgs e)
        {
            if (!hasCompleted)
                return;

            RebuildVisualTreeMap(false);
        }

        /// <summary>
        /// Forces an immediate rebuild of the visual tree map.
        /// </summary>
        /// <param name="sender">The originating object.</param>
        /// <param name="e">The arguments associated with this event.</param>
        private void RefreshClicked(object sender, RoutedEventArgs e)
        {
            RebuildVisualTreeMap(true);
        }
    }
}
